package com.iogogogo.activity.aspect.impl;

import com.iogogogo.activity.annotation.Activity;
import com.iogogogo.activity.aspect.IActivityAspect;
import com.iogogogo.activity.domain.dto.ActivityDTO;
import com.iogogogo.activity.enums.ActivityEventEnum;
import com.iogogogo.activity.enums.ActivityTypeEnum;
import com.iogogogo.activity.message.ActivityMessage;
import com.iogogogo.activity.util.EnumUtils;
import com.iogogogo.activity.util.Java8DateTimeUtils;
import com.iogogogo.activity.util.SpelUtils;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.BeansException;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.core.PriorityOrdered;
import org.springframework.expression.EvaluationContext;
import org.springframework.util.StringUtils;

import java.lang.reflect.Method;
import java.time.LocalDateTime;
import java.util.concurrent.Executor;

/**
 * https://blog.csdn.net/u010675669/article/details/109010042
 * <p>
 * https://blog.csdn.net/yunxing323/article/details/108655250
 * <p>
 * Spring 5.2.0+的版本，建议你的配置类均采用Lite模式去做，即显示设置proxyBeanMethods = false。
 * Spring Boot在2.2.0版本（依赖于Spring 5.2.0）起就把它的所有的自动配置类的此属性改为了false，即@Configuration(proxyBeanMethods = false)，提高Spring启动速度
 * <p>
 * proxyBeanMethods = false ==> 告诉springboot这是一个配置类 == 配置文件
 * <p>
 * Created by tao.zeng on 2022/6/15.
 */
@Slf4j
@Aspect
@Configuration(proxyBeanMethods = false)
@EnableAspectJAutoProxy(exposeProxy = true, proxyTargetClass = true)
@ConditionalOnProperty(prefix = "activity", name = "enable", havingValue = "true", matchIfMissing = true)
public class DefaultActivityAspect implements PriorityOrdered, ApplicationContextAware, IActivityAspect {

    private ApplicationContext applicationContext;

    private final Executor executor;

    public DefaultActivityAspect(Executor executor) {
        this.executor = executor;
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    @Override
    public int getOrder() {
        // 保证事务等切面先执行
        return Integer.MAX_VALUE;
    }


    @Pointcut("@annotation(com.iogogogo.activity.annotation.Activity)")
    public void activity() {
    }

    @Around("activity()")
    public Object doAround(ProceedingJoinPoint joinPoint) {
        try {
            process(joinPoint);
            return joinPoint.proceed();
        } catch (Throwable e) {
            throw new RuntimeException(e);
        }
    }

    private void process(ProceedingJoinPoint joinPoint) {
        // 子线程处理逻辑，避免阻塞主线程流程处理
        executor.execute(() -> {
            // 获取方法签名(通过此签名获取目标方法信息)
            MethodSignature signature = (MethodSignature) joinPoint.getSignature();
            Method targetMethod = signature.getMethod();
            if (targetMethod.isAnnotationPresent(Activity.class)) {
                Activity activity = targetMethod.getAnnotation(Activity.class);

                EvaluationContext context = SpelUtils.getContext(joinPoint.getArgs(), signature.getMethod());

                Object sourceValue = null, targetValue = null;
                String descriptionValue = null;

                String source = activity.source();
                if (StringUtils.hasLength(source)) {
                    sourceValue = SpelUtils.getValue(context, source, activity.sourceType());
                    if (log.isDebugEnabled()) log.debug("sourceValue ===> {}", sourceValue);
                }

                String target = activity.target();
                if (StringUtils.hasLength(target)) {
                    targetValue = SpelUtils.getValue(context, target, activity.targetType());
                    if (log.isDebugEnabled()) log.debug("targetValue ===> {}", targetValue);
                }

                String description = activity.description();
                if (StringUtils.hasLength(description)) {
                    descriptionValue = SpelUtils.getValue(context, description, String.class);
                    if (log.isDebugEnabled()) log.debug("descriptionValue ===> {}", descriptionValue);
                }

                publishEvent(activity, sourceValue, targetValue, descriptionValue);
            }
        });
    }

    private void publishEvent(Activity activity, Object sourceValue, Object targetValue, String descriptionValue) {
        ActivityTypeEnum typeEnum = activity.type();
        ActivityEventEnum eventEnum = activity.event();
        int status = activity.status();
        LocalDateTime timestamp = Java8DateTimeUtils.nowDateTime();

        ActivityDTO<Object, Object> activityDTO = new ActivityDTO<>();
        activityDTO.setStatus(status);
        activityDTO.setTimestamp(timestamp);
        activityDTO.setType(EnumUtils.toMap(typeEnum));
        activityDTO.setEvent(EnumUtils.toMap(eventEnum));

        activityDTO.setSource(sourceValue);
        activityDTO.setTarget(targetValue);
        activityDTO.setDescription(descriptionValue);

        applicationContext.publishEvent(new ActivityMessage(activityDTO));
    }
}
